/*
 *  Copyright 2011 Alexey Andreev.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package org.xthl.core;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.nop.core.Application;
import org.nop.core.ApplicationHandler;
import org.nop.sql.CloseableDataManager;
import org.nop.sql.DataManagerFactory;
import org.nop.sql.DataResult;
import org.nop.sql.QueryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xthl.core.data.FragmentSource;
import org.xthl.core.data.FragmentTypeSource;

/**
 *
 * @author Alexey Andreev
 */
public class FragmentsFeatureImpl implements FragmentsFeature {
    private static final Logger logger = LoggerFactory.getLogger(FragmentsFeatureImpl.class);
    private Application app;
    private Map<Class<? extends FragmentRenderer>, Set<Class<? extends FragmentRenderer>>>
            fragments = new HashMap<Class<? extends FragmentRenderer>, 
            Set<Class<? extends FragmentRenderer>>>();
    private Map<Integer, Class<? extends FragmentRenderer>> typesByIds =
            new HashMap<Integer, Class<? extends FragmentRenderer>>();
    private Map<Integer, Class<? extends FragmentRenderer>> fragmentsByIds =
            new HashMap<Integer, Class<? extends FragmentRenderer>>();

    public FragmentsFeatureImpl(Application app) {
        this.app = app;
        app.addApplicationHandler(new ApplicationHandler() {
            @Override
            public void started() {
                initFragments();
            }
        });
    }

    @Override
    public void registerFragment(Class<? extends FragmentRenderer> fragmentType,
            Class<? extends FragmentRenderer> fragmentRenderer) {
        Set<Class<? extends FragmentRenderer>> renderers = fragments.get(fragmentType);
        if (renderers == null) {
            renderers = new HashSet<Class<? extends FragmentRenderer>>();
            fragments.put(fragmentType, renderers);
        }
        renderers.add(fragmentRenderer);
    }
    
    @Override
    public Class<? extends FragmentRenderer> getFragmentType(int id) {
        return typesByIds.get(id);
    }

    @Override
    public Class<? extends FragmentRenderer> getFragment(int id) {
        return fragmentsByIds.get(id);
    }

    private void initFragments() {
        fragments.remove(FragmentRenderer.class);
        DataManagerFactory dmf = app.get(DataManagerFactory.class);
        CloseableDataManager dataManager = dmf.createDataManager();
        try {
            QueryBuilder qb = dataManager.getQueryBuilder();
            FragmentTypeSource type = qb.get(FragmentTypeSource.class);
            FragmentSource fragment = qb.get(FragmentSource.class);
            
            Set<Class<? extends FragmentRenderer>> typesToCreate = 
                    new HashSet<Class<? extends FragmentRenderer>>(fragments.keySet());
            Map<Class<?>, Integer> typeIds = new HashMap<Class<?>, Integer>();
            DataResult result = dataManager.exec(qb.with(type)
                    .fetch(type.id(), type.name()));
            while (result.next()) {
                String name = result.getString(2);
                try {
                    Class<?> cls = Class.forName(name);
                    if (FragmentRenderer.class.isAssignableFrom(cls)) {
                        typesByIds.put(result.getInt(1), cls.asSubclass(FragmentRenderer.class));
                        typeIds.put(cls, result.getInt(1));
                        typesToCreate.remove(cls.asSubclass(FragmentRenderer.class));
                    } else {
                        logger.warn("Fragment type class {} does not implement {}", 
                                name, FragmentRenderer.class.getName());
                    }
                } catch (ClassNotFoundException ex) {
                    logger.warn("Fragment type class not found: {}", name);
                    continue;
                }
            }
            
            for (Class<? extends FragmentRenderer> typeCls : fragments.keySet()) {
                if (typesToCreate.contains(typeCls)) {
                    int id = dataManager.nextInt(FragmentTypeSource.ID_SEQUENCE);
                    dataManager.exec(qb.insertInto(type)
                            .field(type.id(), id)
                            .field(type.name(), typeCls.getName()));
                    typesByIds.put(id, typeCls);
                    int pos = 1;
                    for (Class<? extends FragmentRenderer> fragmentCls : fragments.get(typeCls)) {
                        int fragmentId = dataManager.nextInt(FragmentSource.ID_SEQUENCE);
                        dataManager.exec(qb.insertInto(fragment)
                                .field(fragment.id(), fragmentId)
                                .field(fragment.enabled(), true)
                                .field(fragment.name(), fragmentCls.getName())
                                .field(fragment.position(), pos++)
                                .field(fragment.typeId(), id));
                        fragmentsByIds.put(fragmentId, fragmentCls);
                    }
                } else {
                    int id = typeIds.get(typeCls);
                    result = dataManager.exec(qb.with(fragment)
                            .filter(fragment.typeId().eq(id))
                            .sortAsc(fragment.id())
                            .fetch(fragment.name(), fragment.id(), fragment.position()));
                    int pos = 0;
                    Set<Class<? extends FragmentRenderer>> fragmentsToCreate = 
                            new HashSet<Class<? extends FragmentRenderer>>(fragments.get(typeCls));
                    while (result.next()) {
                        pos = Math.max(pos, result.getInt(3));
                        String name = result.getString(1);
                        try {
                            Class<?> cls = Class.forName(name);
                            if (!FragmentRenderer.class.isAssignableFrom(cls)) {
                                logger.warn("Fragment class {} is not subclass of {}", name,
                                        FragmentRenderer.class.getName());
                                continue;
                            }
                            fragmentsToCreate.remove(cls.asSubclass(FragmentRenderer.class));
                            fragmentsByIds.put(result.getInt(2),
                                    cls.asSubclass(FragmentRenderer.class));
                        } catch (ClassNotFoundException ex) {
                            logger.warn("Fragment class not found: {}", name);
                        }
                    }
                    for (Class<? extends FragmentRenderer> fragmentCls : fragmentsToCreate) {
                        int fragmentId = dataManager.nextInt(FragmentSource.ID_SEQUENCE);
                        dataManager.exec(qb.insertInto(fragment)
                                .field(fragment.id(), fragmentId)
                                .field(fragment.enabled(), true)
                                .field(fragment.name(), fragmentCls.getName())
                                .field(fragment.position(), pos++)
                                .field(fragment.typeId(), id));
                        fragmentsByIds.put(fragmentId, fragmentCls);
                    }
                }
            }
            
            dataManager.commit();
        } finally {
            dataManager.close();
        }
    }
}
